/*
 * @(#) $Header$
 *
 * Copyright (C) 2007  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.media.jai.opimage;

import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ContextualRenderedImageFactory;
import java.awt.image.renderable.ParameterBlock;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import ca.forklabs.baselib.util.BinaryFunction;
import ca.forklabs.baselib.util.Functions;
import ca.forklabs.baselib.util.UnaryFunction;
import ca.forklabs.media.jai.ParameterBlockUtil;
import ca.forklabs.media.jai.operator.AutoRescaleDescriptor;
import ca.forklabs.media.jai.opimage.AbstractCRIF;

/**
 * Class {@code AutoRescaleCRIF} is a {@link ContextualRenderedImageFactory}
 * supporting the <em>autorescale</em> operation in the rendered and renderable
 * image layers.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.opimage.AutoRescaleCRIF">Daniel Léonard</a>
 * @version $Revision$
 */
public class AutoRescaleCRIF extends AbstractCRIF {

//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    */
   public AutoRescaleCRIF() {
      super(AutoRescaleDescriptor.NAME);
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets the lower bounds from the parameter block.
    * @param   pb   the parameter block.
    * @return   the lower bounds.
    */
   protected double[] getLowerBounds(ParameterBlock pb) {
      int index = AutoRescaleDescriptor.LOWER_BOUND_PARAMETER_INDEX;
      double[] lower_bounds = (double[]) pb.getObjectParameter(index);
      return lower_bounds;
      }

   /**
    * Gets the upper bounds from the parameter block.
    * @param   pb   the parameter block.
    * @return   the upper bounds.
    */
   protected double[] getUpperBounds(ParameterBlock pb) {
      int index = AutoRescaleDescriptor.UPPER_BOUND_PARAMETER_INDEX;
      double[] upper_bounds = (double[]) pb.getObjectParameter(index);
      return upper_bounds;
      }

   /**
    * Gets the function adding the values to the image.
    * @param   values   the values to be added.
    * @return   the function.
    */
   @SuppressWarnings("nls")
   protected BinaryFunction<RenderedImage, RenderedImage, RenderingHints> getAddConstFunction(double[] values) {
      final double[] constants = values;
      BinaryFunction<RenderedImage, RenderedImage, RenderingHints> function = new BinaryFunction<RenderedImage, RenderedImage, RenderingHints>() {
         public RenderedImage invoke(RenderedImage source, RenderingHints hints) {
            ParameterBlock pb = ParameterBlockUtil.createAddConstParameterBlock(source, constants);
            RenderedImage sink = JAI.create("addconst", pb, hints);
            return sink;
            }
         };
      return function;
      }

   /**
    * Gets the function rescaling the image values between
    * <code>[0, upper_bounds - lower_bounds]</code>.
    * @param   lower_bounds   the lower bounds.
    * @param   upper_bounds   the upper bounds.
    * @return   the function
    */
   @SuppressWarnings("nls")
   protected BinaryFunction<RenderedImage, RenderedImage, RenderingHints> getRescaleFunction(double[] lower_bounds, double[] upper_bounds) {
      final int r_len = lower_bounds.length;
      final double[] ranges = new double[r_len];
      for (int i = 0; i < r_len; i++) {
         ranges[i] = upper_bounds[i] - lower_bounds[i];
         }

      BinaryFunction<RenderedImage, RenderedImage, RenderingHints> function = new BinaryFunction<RenderedImage, RenderedImage, RenderingHints>() {
         public RenderedImage invoke(RenderedImage source, RenderingHints hints) {
            double[] minima;
            double[] maxima;
            {
               ParameterBlock pb = ParameterBlockUtil.createExtremaParameterBlock(source);
               RenderedOp sink = JAI.create("extrema", pb, hints);
               minima = (double[]) sink.getProperty("minimum");
               maxima = (double[]) sink.getProperty("maximum");
            }

            int e_len = minima.length;
            double[] constants = new double[e_len];
            double[] offsets = new double[e_len];
            for (int i = 0; i < e_len; i++) {
               double range = (1 == r_len) ? ranges[0] : ranges[i];
            // If the band is constant, the minimal and maximal
            // values will be the same. The selected value is
            // decided to be the middle of the range.
               double min = minima[i];
               double max = maxima[i];
               if (0 != Double.compare(min, max)) {
                  constants[i] = range / (max - min);
                  offsets[i] = (range * min) / (min - max);
                  }
               else {
                  constants[i] = 0.0;
                  offsets[i] = range / 2.0;
                  }
               }

            ParameterBlock pb = ParameterBlockUtil.createRescaleParameterBlock(source, constants, offsets);
            RenderedImage sink = JAI.create("rescale", pb, hints);
            return sink;
            }
         };
      return function;
      }

   /**
    * Gets the function doing the auto-scaling.
    * @param   pb   the parameter blocks.
    * @param   hints   the rendering hints.
    * @return   the function.
    */
   protected UnaryFunction<RenderedImage, RenderedImage> getAutoRescaleFunction(ParameterBlock pb, RenderingHints hints) {
      double[] lower_bounds = this.getLowerBounds(pb);
      double[] upper_bounds = this.getUpperBounds(pb);

      UnaryFunction<RenderedImage, RenderedImage> function =
         Functions.compose1(
            Functions.bind2nd(this.getAddConstFunction(lower_bounds), hints),
            Functions.bind2nd(this.getRescaleFunction(lower_bounds, upper_bounds), hints)
            );
      return function;
      }


//---------------------------
// Implemented methods from javax.media.jai.CRIFImpl
//---------------------------

   /**
    * Enhance the image.
    * @param   pb   the parameter block.
    * @param   hints   optional rendering hints.
    */
   @Override
   public RenderedImage create(ParameterBlock pb, RenderingHints hints) {
      RenderedImage source = (RenderedImage) pb.getSource(0);
      UnaryFunction<RenderedImage, RenderedImage> auto_rescale_function = this.getAutoRescaleFunction(pb, hints);
      RenderedImage sink = auto_rescale_function.invoke(source);
      return sink;
      }

   }
